package packetsender;

import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;


/**
 * 信息包, 该信息包的最大长度为2KB (包括包头包尾以及包号在内)
 * @author 	林	岑
 * @time	2012年6月29日 12:21:19
 */
public class Packet {

	/**
	 * 发给前端用户名的长度
	 */
	public static final int USER_NAME_SIZE = 32;


	/**
	 * 待发送的数据信息
	 */
	protected ByteBuffer info = ByteBuffer.allocate(2048);

	/**
	 * 存放每次放入元素的大小, 这个List只是为了测试和调试用, 正式发布的时候, 可以删掉
	 */
	private List<Short> sizes = new ArrayList<Short>();

	/**
	 * 所有包模式, 如果将其改为RELEASE, 那么在性能上会有很大提高
	 */
	private static final TYPE type = TYPE.DEBUG;
	//	private static final TYPE type = TYPE.RELEASE;

	private enum TYPE {
		DEBUG,		//调试
		RELEASE		//正式版本
	}

	/**
	 * 是否已经发送
	 */
	private boolean isSend = false;

	public Packet() {
		this.info.put((byte) 116);		//包头 长度1
		this.info.putShort(Short.MIN_VALUE);				//包长 长度2
	}
	/**
	 * 将i强制转换后放入包中
	 * @param i
	 */
	public Packet putByte(int i) {
		this.info.put((byte) i);
		if(type == TYPE.DEBUG ) 
			this.sizes.add((short) 1);
		updatePacketSize();
		return this;
	}

	public Packet putShort(int i) {
		this.info.putShort((short) i);
		if(type == TYPE.DEBUG ) 
			this.sizes.add((short) 2);
		updatePacketSize();
		return this;
	}

	public Packet putInt(int i) {
		this.info.putInt(i);
		if(type == TYPE.DEBUG ) 
			this.sizes.add((short) 4);
		updatePacketSize();
		return this;
	}

	/**
	 * 放入一个字符串, 多的截取, 少的补空格: 32
	 * @param str	指定字符串
	 * @param size 	指定长度
	 */
	public void putString(String str, int size) {
		byte [] dat = str.getBytes();
		byte [] temp = new byte[size];
		for (int i = 0; i < size; i++) {
			if(i < dat.length) {
				temp[i] = dat[i];
			} else {
				temp[i] = ' ';
			}
		}
		this.info.put(temp);
		if(type == TYPE.DEBUG ) 
			this.sizes.add((short) size);
		updatePacketSize();
	}

	/**
	 * 放入一个字符串, 以'\0'结尾
	 * @param str
	 * @return 
	 */
	public Packet putString(String str) {
		byte[] bytes = str.getBytes();
		if(bytes == null || bytes.length == 0) {
			return this;
		}
		this.info.put(bytes);
		this.info.put((byte)'\0');
		updatePacketSize();
		if(type == TYPE.DEBUG ) 
			this.sizes.add((short) (bytes.length + 1));
		return this;
	}

	/**
	 * 检查该数据包是否已经发送过了
	 * @return
	 */
	public boolean isSend() {
		return isSend;
	}

	/**
	 * 将包发送给前端, 如果已经发送过了, 那么就不做任何处理
	 */
	public void send(Socket socket) {
		if(!isSend) {

			this.info.putShort(1, (short) this.size() );
			this.info.put((byte) 127);				//包尾 长度1
			this.info.flip();


			try {
				socket.getOutputStream().write(this.info.array());
			} catch (Exception e) {
				System.err.println("包发送出错了: 以下是包的内容, 请检查! + \n");
				System.err.println(this);
				e.printStackTrace();
			}

			isSend = true;

		} else {
			new Exception("该数据包已经发送过了!").printStackTrace();
		}
	}

	/**
	 * 获得包的大小
	 * @return
	 */
	public int size() {
		return this.info.position() - 3;
	}


	@Override
	public String toString() {
		StringBuilder temp = new StringBuilder();
		temp.append("包头");
		temp.append("\t");
		temp.append("包长");
		temp.append("\t");
		temp.append("包号");
		temp.append("\t");
		temp.append("| ");

		temp.append(getSizeTitle());

		temp.append(" |");
		temp.append("\t");
		temp.append("包尾");
		temp.append("\t");
		temp.append("\n");

		temp.append(getContent());

		return temp.toString();
	}

	/**
	 * 更新当前包的大小
	 */
	private void updatePacketSize() {
		this.info.putShort(1, (short) this.size());
	}

	/**
	 * 获取包的内容
	 * @return
	 */
	private String getContent() {
		StringBuilder sb = new StringBuilder();

		int pos = 0;

		sb.append(this.info.get(pos) + "\t");
		pos ++;
		sb.append(this.info.getShort(pos) + "\t");
		pos += 2;
//		sb.append(this.info.getShort(pos) + "\t");
//		pos += 2;


		sb.append("| ");

		for (Short s : this.sizes) {
			sb.append( parseToNumOrStr(this.get(pos, s)) + "\t");
			pos += s;
		}

		sb.append(" |");
		sb.append("\t");


		sb.append(this.info.get(pos) + "\n");
		sb.append("---------------------------------- END -------------------------------------------");
		pos ++;
		return sb.toString();
	}

	/**
	 * 如果 bs的长度不为 1, 2, 4, 那么就将其转换成字符串, 否则就将其转换成数值
	 */
	private String parseToNumOrStr(byte[] bs) {
		switch (bs.length) {
		case 1:
			return bs[0] + "";
		case 2:
			return Util.byteToShort(bs[0], bs[1]) + "";
		case 4:
			return Util.byteToInt(bs[0], bs[1], bs[2], bs[3]) + "";
		default:
			return new String(bs);
		}
	}

	/**
	 * 绝对位置获取方式
	 * @param pos
	 * @param s
	 * @return
	 */
	private byte[] get(int pos, short size) {
		if(size <= 0) {
			return new byte[0];
		}

		byte [] out = new byte[size];
		for (int i = 0; i < size; i++) {
			out[i] = this.info.get(pos++);
		}

		return out;
	}

	/**
	 * 获得由放入时的大小拼接成的字符串
	 * 比如依次调用了 putByte putInt putByte
	 * 那么返回的字符串就是 1	4	1
	 * @return
	 */
	private String getSizeTitle() {
		StringBuilder sb = new StringBuilder();
		for (Short s : this.sizes) {
			switch (s) {
			case 1:
				sb.append("BYTE");
				break;
			case 2:
				sb.append("SHORT");
				break;
			case 4:
				sb.append("INT");
				break;
			default:
				sb.append("STR");
				break;
			}

			sb.append("\t");
		}
		return sb.toString();
	}

	//	public static void main(String[] args) {
	//		Packet packet = new Packet(15, 3);
	//		//		packet.putByte(3,2,4);
	//		//		packet.putShort(0, 7);
	//		//		packet.putInt(4, 1);
	//		packet.putString("Wo");
	//		packet.putString("HelloWorld", 10);
	//		packet.putString("H", 5);
	//		packet.putString("H", 5);
	//		packet.putString("H", 5);
	//		packet.putString("H", 5);
	//		System.out.println(packet);
	//		packet.send(new UserInfoMain());
	//	}

	public int position() {
		return this.info.position();
	}

	/**
	 * 在指定位置放入一个short值
	 * @param index
	 * @param value
	 */
	public void putShort(int index, int value) {
		this.info.putShort(index, (short) value);
	}

	/**
	 * 在指定位置放入一个int值
	 * @param index
	 * @param value
	 */
	public void putInt(int index, int value) {
		this.info.putInt(index, value);
	}

	/**
	 * 绝对放入一个byte
	 * @param index
	 * @param value
	 */
	public void putByte(int index, int value) {
		this.info.put(index, (byte) value);
	}
}
